﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Crm.Sdk;
using Microsoft.Crm.SdkTypeProxy;
using System.Web.Services.Protocols;
using Microsoft.Crm.Sdk.Query;
using System.Xml.Linq;

namespace XrmLinq.Entities
{
    public class CrmDataAccess
    {
        /// <summary>
        /// Deletes a record from CRM.
        /// </summary>
        /// <param name="sdk">Instance of the CRM web service.</param>
        /// <param name="entityId">Uniqueidentifier of the record.</param>
        /// <param name="entityName">Schema name of the record.</param>
        /// <example>
        /// <code>
        /// <![CDATA[
        /// CrmService sdk = CreateCrmConnection();
        /// try
        /// {
        ///     CrmDataAccess.Delete(sdk, Guid.Emty, Account.Fields.Schemaname);
        /// }
        /// catch (CrmException ce)
        /// {
        ///     Console.WriteLine(ce.Message);
        /// }
        /// ]]>
        /// </code>
        /// </example>
        /// <exception cref="CrmException">When the call to sdk.Execute() fails.</exception>
        public static void Delete(CrmService sdk, Guid entityId, string entityName)
        {
            DeleteRequest request = new DeleteRequest
            {
                Target = new TargetDeleteDynamic
                {
                    EntityId = entityId,
                    EntityName = entityName
                }
            };
            try
            {
                DeleteResponse response = sdk.Execute(request) as DeleteResponse;
            }
            catch (SoapException se)
            {
                throw new CrmException(se.Detail.InnerText, se);
            }
        }

        /// <summary>
        /// Deletes a record from CRM. This method is using ICrmService.
        /// </summary>
        /// <param name="sdk">Instance of the CRM web service.</param>
        /// <param name="entityId">Uniqueidentifier of the record.</param>
        /// <param name="entityName">Schema name of the record.</param>
        /// <example>
        /// <code>
        /// <![CDATA[
        /// ICrmService sdk = GetICrmService();
        /// try
        /// {
        ///     CrmDataAccess.Delete(sdk, Guid.Emty, Account.Fields.Schemaname);
        /// }
        /// catch (CrmException ce)
        /// {
        ///     Console.WriteLine(ce.Message);
        /// }
        /// ]]>
        /// </code>
        /// </example>
        /// <exception cref="CrmException">When the call to sdk.Execute() fails.</exception>
        public static void Delete(ICrmService sdk, Guid entityId, string entityName)
        {
            DeleteRequest request = new DeleteRequest
            {
                Target = new TargetDeleteDynamic
                {
                    EntityId = entityId,
                    EntityName = entityName
                }
            };
            try
            {
                DeleteResponse response = sdk.Execute(request) as DeleteResponse;
            }
            catch (SoapException se)
            {
                throw new CrmException(se.Detail.InnerText, se);
            }
        }

        /// <summary>
        /// Create a record in CRM.
        /// </summary>
        /// <typeparam name="T">Type of the entity.</typeparam>
        /// <param name="sdk">Instance of the CRM web service.</param>
        /// <param name="entity">Instance of the entity. It must derive from <see cref="CrmEntity"/>.</param>
        /// <example>
        /// <code>
        /// <![CDATA[
        /// CrmService sdk = CreateCrmConnection();
        /// Account myAccount = new Account
        /// {
        ///     AccountId = Guid.NewGuid(),
        ///     AccountName = "xRM LiNQ"
        /// };
        /// try
        /// {
        ///     CrmDataAccess.Create<Account>(sdk, myAccount);
        /// }
        /// catch (CrmException ce)
        /// {
        ///     Console.WriteLine(ce.Message);
        /// }
        /// ]]>
        /// </code>
        /// </example>
        /// <exception cref="CrmException">When the call to sdk.Execute() fails.</exception>
        public static void Create<T>(CrmService sdk, T entity) where T : CrmEntity
        {
            CreateRequest request = new CreateRequest
            {
                Target = new TargetCreateDynamic
                {
                    Entity = entity.ToDynamic()
                }
            };
            try
            {
                CreateResponse response = sdk.Execute(request) as CreateResponse;
            }
            catch (SoapException se)
            {
                throw new CrmException(se.Detail.InnerText, se);
            }
        }

        /// <summary>
        /// Create a record in CRM. This method is using ICrmService.
        /// </summary>
        /// <typeparam name="T">Type of the entity.</typeparam>
        /// <param name="sdk">Instance of the CRM web service.</param>
        /// <param name="entity">Instance of the entity. It must derive from <see cref="CrmEntity"/>.</param>
        /// <example>
        /// <code>
        /// <![CDATA[
        /// ICrmService sdk = GetICrmService();
        /// Account myAccount = new Account
        /// {
        ///     AccountId = Guid.NewGuid(),
        ///     AccountName = "xRM LiNQ"
        /// };
        /// try
        /// {
        ///     CrmDataAccess.Create<Account>(sdk, myAccount);
        /// }
        /// catch (CrmException ce)
        /// {
        ///     Console.WriteLine(ce.Message);
        /// }
        /// ]]>
        /// </code>
        /// </example>
        /// <exception cref="CrmException">When the call to sdk.Execute() fails.</exception>
        public static void Create<T>(ICrmService sdk, T entity) where T : CrmEntity
        {
            CreateRequest request = new CreateRequest
            {
                Target = new TargetCreateDynamic
                {
                    Entity = entity.ToDynamic()
                }
            };
            try
            {
                CreateResponse response = sdk.Execute(request) as CreateResponse;
            }
            catch (SoapException se)
            {
                throw new CrmException(se.Detail.InnerText, se);
            }
        }

        /// <summary>
        /// Updates a record in CRM.
        /// </summary>
        /// <typeparam name="T">Type of the entity.</typeparam>
        /// <param name="sdk">Instance of the CRM web service.</param>
        /// <param name="entity">Instance of the entity.</param>
        /// <example>
        /// <code>
        /// <![CDATA[
        /// CrmService sdk = CreateCrmConnection();
        /// try
        /// {
        ///     Account myAccount = CrmDataAccess.Retrieve<Account>(sdk, Account.Fields.SchemaName, Account.Fields.AccountId, Guid.Empty);
        ///     if (myAccount != null)
        ///     {
        ///         myAccount.AccountName = "Changed Name";
        ///         CrmDataAccess.Update<Account>(sdk, myAccount);
        ///     }
        /// }
        /// catch (CrmException ce)
        /// {
        ///     Console.WriteLine(ce.Message);
        /// }
        /// ]]>
        /// </code>
        /// </example>
        /// <exception cref="CrmException">When the call to sdk.Execute() fails.</exception>
        public static void Update<T>(CrmService sdk, T entity) where T : CrmEntity
        {
            UpdateRequest request = new UpdateRequest
            {
                Target = new TargetUpdateDynamic
                {
                    Entity = entity.ToDynamic()
                }
            };
            try
            {
                UpdateResponse response = sdk.Execute(request) as UpdateResponse;
            }
            catch (SoapException se)
            {
                throw new CrmException(se.Detail.InnerText, se);
            }
        }

        /// <summary>
        /// Updates a record in CRM. This method is using ICrmService.
        /// </summary>
        /// <typeparam name="T">Type of the entity.</typeparam>
        /// <param name="sdk">Instance of the CRM web service.</param>
        /// <param name="entity">Instance of the entity.</param>
        /// <example>
        /// <code>
        /// <![CDATA[
        /// ICrmService sdk = GetICrmService();
        /// try
        /// {
        ///     Account myAccount = CrmDataAccess.Retrieve<Account>(sdk, Account.Fields.SchemaName, Account.Fields.AccountId, Guid.Empty);
        ///     if (myAccount != null)
        ///     {
        ///         myAccount.AccountName = "Changed Name";
        ///         CrmDataAccess.Update<Account>(sdk, myAccount);
        ///     }
        /// }
        /// catch (CrmException ce)
        /// {
        ///     Console.WriteLine(ce.Message);
        /// }
        /// ]]>
        /// </code>
        /// </example>
        /// <exception cref="CrmException">When the call to sdk.Execute() fails.</exception>
        public static void Update<T>(ICrmService sdk, T entity) where T : CrmEntity
        {
            UpdateRequest request = new UpdateRequest
            {
                Target = new TargetUpdateDynamic
                {
                    Entity = entity.ToDynamic()
                }
            };
            try
            {
                UpdateResponse response = sdk.Execute(request) as UpdateResponse;
            }
            catch (SoapException se)
            {
                throw new CrmException(se.Detail.InnerText, se);
            }
        }

        /// <summary>
        /// Retrieve a record from CRM using it's uniqueidentifier, this method is used by the lazy loaded properties. It automatically handles CrmService and ICrmService.
        /// </summary>
        /// <typeparam name="T">Type of the entity.</typeparam>
        /// <param name="sdk">Instance of the CRM web service.</param>
        /// <param name="isdk">Instance of the CRM web service interface.</param>
        /// <param name="entityName">Schema name of the entity.</param>
        /// <param name="keyField">Schema name of the primay key field.</param>
        /// <param name="key">Uniqueidentifier of this record.</param>
        /// <returns>An instance of T.</returns>
        public static T Retrieve<T>(CrmService sdk, ICrmService isdk, string entityName, string keyField, Guid key) where T : CrmEntity, new()
        {
            if (sdk != null)
            {
                return Retrieve<T>(sdk, entityName, keyField, key);
            }
            else if (isdk != null)
            {
                return Retrieve<T>(isdk, entityName, keyField, key);
            }
            return null;
        }

        /// <summary>
        /// Retrieve a record from CRM using it's uniqueidentifier.
        /// </summary>
        /// <typeparam name="T">Type of the entity.</typeparam>
        /// <param name="sdk">Instance of the CRM web service.</param>
        /// <param name="entityName">Schema name of the entity.</param>
        /// <param name="keyField">Schema name of the primay key field.</param>
        /// <param name="key">Uniqueidentifier of this record.</param>
        /// <returns>An instance of T.</returns>
        /// <example>
        /// <code>
        /// <![CDATA[
        /// CrmService sdk = CreateCrmConnection();
        /// try
        /// {
        ///     Account myAccount = CrmDataAccess.Retrieve<Account>(sdk, Account.Fields.SchemaName, Account.Fields.AccountId, Guid.Empty);
        ///     if (myAccount != null)
        ///     {
        ///         Console.WriteLine("Account Name is: {0}", myAccount.AccountName);
        ///     }
        /// }
        /// catch (CrmException ce)
        /// {
        ///     Console.WriteLine(ce.Message);
        /// }
        /// ]]>
        /// </code>
        /// </example>
        /// <exception cref="CrmException">When the call to sdk.Execute() fails.</exception>
        public static T Retrieve<T>(CrmService sdk, string entityName, string keyField, Guid key) where T : CrmEntity, new()
        {
            List<T> result = new List<T>();

            QueryExpression query = new QueryExpression();

            query.EntityName = entityName;
            query.ColumnSet = new AllColumns();

            query.Criteria = new FilterExpression();
            query.Criteria.FilterOperator = LogicalOperator.And;

            ConditionExpression condition1 = new ConditionExpression();
            condition1.AttributeName = keyField;
            condition1.Operator = ConditionOperator.Equal;
            condition1.Values = new object[] { key };

            query.Criteria.AddCondition(condition1);

            RetrieveMultipleRequest rmr = new RetrieveMultipleRequest();
            rmr.ReturnDynamicEntities = true;
            rmr.Query = query;

            RetrieveMultipleResponse rmresponse = null;
            try
            {
                rmresponse = sdk.Execute(rmr) as RetrieveMultipleResponse;
            }
            catch (SoapException se)
            {
                throw new CrmException(se.Detail.InnerText, se);
            }
            foreach (DynamicEntity entity in rmresponse.BusinessEntityCollection.BusinessEntities)
            {
                T current = new T();
                current.Initialize(sdk, entity);

                result.Add(current);
            }
            return (result.Count > 0) ? result[0] : null;
        }

        /// <summary>
        /// Retrieve a record from CRM using it's uniqueidentifier. This method is using ICrmService.
        /// </summary>
        /// <typeparam name="T">Type of the entity.</typeparam>
        /// <param name="sdk">Instance of the CRM web service.</param>
        /// <param name="entityName">Schema name of the entity.</param>
        /// <param name="keyField">Schema name of the primay key field.</param>
        /// <param name="key">Uniqueidentifier of this record.</param>
        /// <returns>An instance of T.</returns>
        /// <example>
        /// <code>
        /// <![CDATA[
        /// ICrmService sdk = GetICrmService();
        /// try
        /// {
        ///     Account myAccount = CrmDataAccess.Retrieve<Account>(sdk, Account.Fields.SchemaName, Account.Fields.AccountId, Guid.Empty);
        ///     if (myAccount != null)
        ///     {
        ///         Console.WriteLine("Account Name is: {0}", myAccount.AccountName);
        ///     }
        /// }
        /// catch (CrmException ce)
        /// {
        ///     Console.WriteLine(ce.Message);
        /// }
        /// ]]>
        /// </code>
        /// </example>
        /// <exception cref="CrmException">When the call to sdk.Execute() fails.</exception>
        public static T Retrieve<T>(ICrmService sdk, string entityName, string keyField, Guid key) where T : CrmEntity, new()
        {
            List<T> result = new List<T>();

            QueryExpression query = new QueryExpression();

            query.EntityName = entityName;
            query.ColumnSet = new AllColumns();

            query.Criteria = new FilterExpression();
            query.Criteria.FilterOperator = LogicalOperator.And;

            ConditionExpression condition1 = new ConditionExpression();
            condition1.AttributeName = keyField;
            condition1.Operator = ConditionOperator.Equal;
            condition1.Values = new object[] { key };

            query.Criteria.AddCondition(condition1);

            RetrieveMultipleRequest rmr = new RetrieveMultipleRequest();
            rmr.ReturnDynamicEntities = true;
            rmr.Query = query;

            RetrieveMultipleResponse rmresponse = null;
            try
            {
                rmresponse = sdk.Execute(rmr) as RetrieveMultipleResponse;
            }
            catch (SoapException se)
            {
                throw new CrmException(se.Detail.InnerText, se);
            }
            foreach (DynamicEntity entity in rmresponse.BusinessEntityCollection.BusinessEntities)
            {
                T current = new T();
                current.Initialize(sdk, entity);

                result.Add(current);
            }
            return (result.Count > 0) ? result[0] : null;
        }

        /// <summary>
        /// Retrieve a list of records from CRM. This can be used to retrieve One-to-Many records.
        /// </summary>
        /// <typeparam name="T">Type of the entity.</typeparam>
        /// <param name="sdk">Instance of the CRM web service.</param>
        /// <param name="entityName">Schema name of the entity.</param>
        /// <param name="keyField">Schema name of the key field.</param>
        /// <param name="key">Value of the key field.</param>
        /// <returns>A collection of T.</returns>
        /// <example>
        /// <para>
        /// Retrieve a list of contacts that are part of an account.
        /// </para>
        /// <code>
        /// <![CDATA[
        /// CrmService sdk = CreateCrmConnection();
        /// try
        /// {
        ///     Guid accountId = Guid.Empty;
        ///     Account account = CrmDataAccess.Retrieve<Account>(sdk, Account.Fields.SchemaName, Account.Fields.AccountId, accountId);
        ///     if (account != null)
        ///     {
        ///         List<Contact> contacts = CrmDataAccess.Retrieve<Contact>(sdk, Contact.Fields.SchemaName, Contact.Fields.ParentCustomerId, accountId);
        ///         Console.WriteLine("List of contacts for: {0}", account.AccountName);
        ///         foreach (var contact in contacts)
        ///         {
        ///             Console.WriteLine(contact.FullName);
        ///         }
        ///     }
        /// }
        /// catch (CrmException ce)
        /// {
        ///     Console.WriteLine(ce.Message);
        /// }
        /// ]]>
        /// </code>
        /// <para>
        /// Other option is to use the Lazy Loaded properties.
        /// <code>
        /// <![CDATA[
        /// CrmService sdk = CreateCrmConnection();
        /// try
        /// {
        ///     Guid accountId = Guid.Empty;
        ///     Account account = CrmDataAccess.Retrieve<Account>(sdk, Account.Fields.SchemaName, Account.Fields.AccountId, accountId);
        ///     if (account != null)
        ///     {
        ///         Console.WriteLine("List of contacts for: {0}", account.AccountName);
        ///         foreach (var contact in account.Contacts)
        ///         {
        ///             Console.WriteLine(contact.FullName);
        ///         }
        ///     }
        /// }
        /// catch (CrmException ce)
        /// {
        ///     Console.WriteLine(ce.Message);
        /// }
        /// ]]>
        /// </code>
        /// </para>
        /// </example>
        /// <exception cref="CrmException">When the call to sdk.Execute() fails.</exception>
        public static List<T> RetrieveMultiple<T>(CrmService sdk, string entityName, string keyField, Guid key) where T : CrmEntity, new()
        {
            List<T> result = new List<T>();

            QueryExpression query = new QueryExpression();

            query.EntityName = entityName;
            query.ColumnSet = new AllColumns();

            query.Criteria = new FilterExpression();
            query.Criteria.FilterOperator = LogicalOperator.And;

            ConditionExpression condition1 = new ConditionExpression();
            condition1.AttributeName = keyField;
            condition1.Operator = ConditionOperator.Equal;
            condition1.Values = new object[] { key };

            query.Criteria.AddCondition(condition1);

            RetrieveMultipleRequest rmr = new RetrieveMultipleRequest();
            rmr.ReturnDynamicEntities = true;
            rmr.Query = query;

            RetrieveMultipleResponse rmresponse = null;
            try
            {
                rmresponse = sdk.Execute(rmr) as RetrieveMultipleResponse;
            }
            catch (SoapException se)
            {
                throw new CrmException(se.Detail.InnerText, se);
            }
            foreach (DynamicEntity entity in rmresponse.BusinessEntityCollection.BusinessEntities)
            {
                T current = new T();
                current.Initialize(sdk, entity);

                result.Add(current);
            }
            return result;
        }

        /// <summary>
        /// Retrieve a list of records from CRM. This can be used to retrieve One-to-Many records. This method is using ICrmService.
        /// </summary>
        /// <typeparam name="T">Type of the entity.</typeparam>
        /// <param name="sdk">Instance of the CRM web service.</param>
        /// <param name="entityName">Schema name of the entity.</param>
        /// <param name="keyField">Schema name of the key field.</param>
        /// <param name="key">Value of the key field.</param>
        /// <returns>A collection of T.</returns>
        /// <example>
        /// <para>
        /// Retrieve a list of contacts that are part of an account.
        /// </para>
        /// <code>
        /// <![CDATA[
        /// ICrmService sdk = GetICrmService();
        /// try
        /// {
        ///     Guid accountId = Guid.Empty;
        ///     Account account = CrmDataAccess.Retrieve<Account>(sdk, Account.Fields.SchemaName, Account.Fields.AccountId, accountId);
        ///     if (account != null)
        ///     {
        ///         List<Contact> contacts = CrmDataAccess.Retrieve<Contact>(sdk, Contact.Fields.SchemaName, Contact.Fields.ParentCustomerId, accountId);
        ///         Console.WriteLine("List of contacts for: {0}", account.AccountName);
        ///         foreach (var contact in contacts)
        ///         {
        ///             Console.WriteLine(contact.FullName);
        ///         }
        ///     }
        /// }
        /// catch (CrmException ce)
        /// {
        ///     Console.WriteLine(ce.Message);
        /// }
        /// ]]>
        /// </code>
        /// <para>
        /// Other option is to use the Lazy Loaded properties.
        /// <code>
        /// <![CDATA[
        /// ICrmService sdk = GetICrmService();
        /// try
        /// {
        ///     Guid accountId = Guid.Empty;
        ///     Account account = CrmDataAccess.Retrieve<Account>(sdk, Account.Fields.SchemaName, Account.Fields.AccountId, accountId);
        ///     if (account != null)
        ///     {
        ///         Console.WriteLine("List of contacts for: {0}", account.AccountName);
        ///         foreach (var contact in account.Contacts)
        ///         {
        ///             Console.WriteLine(contact.FullName);
        ///         }
        ///     }
        /// }
        /// catch (CrmException ce)
        /// {
        ///     Console.WriteLine(ce.Message);
        /// }
        /// ]]>
        /// </code>
        /// </para>
        /// </example>
        /// <exception cref="CrmException">When the call to sdk.Execute() fails.</exception>
        public static List<T> RetrieveMultiple<T>(ICrmService sdk, string entityName, string keyField, Guid key) where T : CrmEntity, new()
        {
            List<T> result = new List<T>();

            QueryExpression query = new QueryExpression();

            query.EntityName = entityName;
            query.ColumnSet = new AllColumns();

            query.Criteria = new FilterExpression();
            query.Criteria.FilterOperator = LogicalOperator.And;

            ConditionExpression condition1 = new ConditionExpression();
            condition1.AttributeName = keyField;
            condition1.Operator = ConditionOperator.Equal;
            condition1.Values = new object[] { key };

            query.Criteria.AddCondition(condition1);

            RetrieveMultipleRequest rmr = new RetrieveMultipleRequest();
            rmr.ReturnDynamicEntities = true;
            rmr.Query = query;

            RetrieveMultipleResponse rmresponse = null;
            try
            {
                rmresponse = sdk.Execute(rmr) as RetrieveMultipleResponse;
            }
            catch (SoapException se)
            {
                throw new CrmException(se.Detail.InnerText, se);
            }
            foreach (DynamicEntity entity in rmresponse.BusinessEntityCollection.BusinessEntities)
            {
                T current = new T();
                current.Initialize(sdk, entity);

                result.Add(current);
            }
            return result;
        }

        /// <summary>
        /// Retrieves Many-to-Many records from CRM. This method is used by the lazy loaded properties. It automatically handles CrmService and ICrmService.
        /// </summary>
        /// <typeparam name="T">Type of the entity to return.</typeparam>
        /// <param name="sdk">Instance of the CRM web service.</param>
        /// <param name="isdk">Instance of the CRM web service interface.</param>
        /// <param name="intersectEntityName">Schema name of the intersect entity.</param>
        /// <param name="parentKey">Schema name of the first key field.</param>
        /// <param name="childKey">Schema name of the second key field.</param>
        /// <param name="childEntityName">Schema name of T.</param>
        /// <param name="key">Value of the key.</param>
        /// <returns>A collection of T.</returns>
        public static List<T> RetrieveManyToMany<T>(CrmService sdk, ICrmService isdk, string intersectEntityName, string parentKey, string childKey, string childEntityName, Guid key) where T : CrmEntity, new()
        {
            if (sdk != null)
            {
                return RetrieveManyToMany<T>(sdk, intersectEntityName, parentKey, childKey, childEntityName, key);
            }
            else if (isdk != null)
            {
                return RetrieveManyToMany<T>(isdk, intersectEntityName, parentKey, childKey, childEntityName, key);
            }
            return null;
        }

        /// <summary>
        /// Retrieves Many-to-Many records from CRM.
        /// </summary>
        /// <typeparam name="T">Type of the entity to return.</typeparam>
        /// <param name="sdk">Instance of the CRM web service.</param>
        /// <param name="intersectEntityName">Schema name of the intersect entity.</param>
        /// <param name="parentKey">Schema name of the first key field.</param>
        /// <param name="childKey">Schema name of the second key field.</param>
        /// <param name="childEntityName">Schema name of T.</param>
        /// <param name="key">Value of the key.</param>
        /// <returns>A collection of T.</returns>
        /// <example>
        /// <para>
        /// Retrieve a list of users belonging to a specific role.
        /// </para>
        /// <code>
        /// <![CDATA[
        /// CrmService sdk = CreateCrmConnection();
        /// try
        /// {
        ///     Guid roleId = Guid.Empty;
        ///     List<User> users = CrmDataAccess.RetrieveManyToMany<User>(this.Sdk, "systemuserroles", "roleid", "systemuserid", "systemuser", roleId);
        ///     foreach (var user in users)
        ///     {
        ///         Console.WriteLine("User: {0}", user.FullName);
        ///     }
        /// }
        /// catch (CrmException ce)
        /// {
        ///     Console.WriteLine(ce.Message);
        /// }
        /// ]]>
        /// </code>
        /// </example>
        /// /// <example>
        /// <para>
        /// Retrieve a list of roles that a specific user has.
        /// </para>
        /// <code>
        /// <![CDATA[
        /// CrmService sdk = CreateCrmConnection();
        /// try
        /// {
        ///     Guid userId = Guid.Empty;
        ///     CrmDataAccess.RetrieveManyToMany<Role>(this.Sdk, "systemuserroles", "systemuserid", "roleid", "role", userId);
        ///     foreach (var role in roles)
        ///     {
        ///         Console.WriteLine("Role: {0}", role.Name);
        ///     }
        /// }
        /// catch (CrmException ce)
        /// {
        ///     Console.WriteLine(ce.Message);
        /// }
        /// ]]>
        /// </code>
        /// </example>
        /// <exception cref="CrmException">When the call to sdk.Execute() fails.</exception>
        public static List<T> RetrieveManyToMany<T>(CrmService sdk, string intersectEntityName, string parentKey, string childKey, string childEntityName, Guid key) where T : CrmEntity, new()
        {
            List<T> results = new List<T>();
            string fetchXml = string.Format("<fetch mapping=\"logical\"><entity name=\"{0}\"><all-attributes /><filter type=\"and\"><condition attribute=\"{1}\" operator=\"eq\" value=\"{2}\" /></filter></entity></fetch>", intersectEntityName, parentKey, key);
            string result = "";

            try
            {
                result = sdk.Fetch(fetchXml);
            }
            catch (SoapException se)
            {
                throw new CrmException(se.Detail.InnerText);
            }
            if (!string.IsNullOrEmpty(result))
            {
                XDocument xdoc = XDocument.Parse(result);
                var rows = (from data in xdoc.Descendants("resultset").Descendants("result")
                            select new Guid(data.Element(childKey).Value)).ToList();

                if (rows.Count > 0)
                {
                    QueryExpression query = new QueryExpression();

                    query.EntityName = childEntityName;
                    query.ColumnSet = new AllColumns();

                    query.Criteria = new FilterExpression();
                    query.Criteria.FilterOperator = LogicalOperator.And;

                    ConditionExpression condition1 = new ConditionExpression();
                    condition1.AttributeName = childKey;
                    condition1.Operator = ConditionOperator.In;
                    condition1.Values = rows.Cast<object>().ToArray();

                    query.Criteria.AddCondition(condition1);

                    RetrieveMultipleRequest rmr = new RetrieveMultipleRequest();
                    rmr.ReturnDynamicEntities = true;
                    rmr.Query = query;

                    RetrieveMultipleResponse rmresponse = null;
                    try
                    {
                        rmresponse = sdk.Execute(rmr) as RetrieveMultipleResponse;
                    }
                    catch (SoapException se)
                    {
                        throw new CrmException(se.Detail.InnerText);
                    }
                    foreach (DynamicEntity entity in rmresponse.BusinessEntityCollection.BusinessEntities)
                    {
                        T current = new T();
                        current.Initialize(sdk, entity);

                        results.Add(current);
                    }
                }
            }

            return results;
        }

        /// <summary>
        /// Retrieves Many-to-Many records from CRM. This method is using ICrmService.
        /// </summary>
        /// <typeparam name="T">Type of the entity to return.</typeparam>
        /// <param name="sdk">Instance of the CRM web service.</param>
        /// <param name="intersectEntityName">Schema name of the intersect entity.</param>
        /// <param name="parentKey">Schema name of the first key field.</param>
        /// <param name="childKey">Schema name of the second key field.</param>
        /// <param name="childEntityName">Schema name of T.</param>
        /// <param name="key">Value of the key.</param>
        /// <returns>A collection of T.</returns>
        /// <example>
        /// <para>
        /// Retrieve a list of users belonging to a specific role.
        /// </para>
        /// <code>
        /// <![CDATA[
        /// ICrmService sdk = GetICrmService();
        /// try
        /// {
        ///     Guid roleId = Guid.Empty;
        ///     List<User> users = CrmDataAccess.RetrieveManyToMany<User>(this.Sdk, "systemuserroles", "roleid", "systemuserid", "systemuser", roleId);
        ///     foreach (var user in users)
        ///     {
        ///         Console.WriteLine("User: {0}", user.FullName);
        ///     }
        /// }
        /// catch (CrmException ce)
        /// {
        ///     Console.WriteLine(ce.Message);
        /// }
        /// ]]>
        /// </code>
        /// </example>
        /// /// <example>
        /// <para>
        /// Retrieve a list of roles that a specific user has.
        /// </para>
        /// <code>
        /// <![CDATA[
        /// ICrmService sdk = GetICrmService();
        /// try
        /// {
        ///     Guid userId = Guid.Empty;
        ///     CrmDataAccess.RetrieveManyToMany<Role>(this.Sdk, "systemuserroles", "systemuserid", "roleid", "role", userId);
        ///     foreach (var role in roles)
        ///     {
        ///         Console.WriteLine("Role: {0}", role.Name);
        ///     }
        /// }
        /// catch (CrmException ce)
        /// {
        ///     Console.WriteLine(ce.Message);
        /// }
        /// ]]>
        /// </code>
        /// </example>
        /// <exception cref="CrmException">When the call to sdk.Execute() fails.</exception>
        public static List<T> RetrieveManyToMany<T>(ICrmService sdk, string intersectEntityName, string parentKey, string childKey, string childEntityName, Guid key) where T : CrmEntity, new()
        {
            List<T> results = new List<T>();
            string fetchXml = string.Format("<fetch mapping=\"logical\"><entity name=\"{0}\"><all-attributes /><filter type=\"and\"><condition attribute=\"{1}\" operator=\"eq\" value=\"{2}\" /></filter></entity></fetch>", intersectEntityName, parentKey, key);
            string result = "";

            try
            {
                result = sdk.Fetch(fetchXml);
            }
            catch (SoapException se)
            {
                throw new CrmException(se.Detail.InnerText);
            }
            if (!string.IsNullOrEmpty(result))
            {
                XDocument xdoc = XDocument.Parse(result);
                var rows = (from data in xdoc.Descendants("resultset").Descendants("result")
                            select new Guid(data.Element(childKey).Value)).ToList();

                if (rows.Count > 0)
                {
                    QueryExpression query = new QueryExpression();

                    query.EntityName = childEntityName;
                    query.ColumnSet = new AllColumns();

                    query.Criteria = new FilterExpression();
                    query.Criteria.FilterOperator = LogicalOperator.And;

                    ConditionExpression condition1 = new ConditionExpression();
                    condition1.AttributeName = childKey;
                    condition1.Operator = ConditionOperator.In;
                    condition1.Values = rows.Cast<object>().ToArray();

                    query.Criteria.AddCondition(condition1);

                    RetrieveMultipleRequest rmr = new RetrieveMultipleRequest();
                    rmr.ReturnDynamicEntities = true;
                    rmr.Query = query;

                    RetrieveMultipleResponse rmresponse = null;
                    try
                    {
                        rmresponse = sdk.Execute(rmr) as RetrieveMultipleResponse;
                    }
                    catch (SoapException se)
                    {
                        throw new CrmException(se.Detail.InnerText);
                    }
                    foreach (DynamicEntity entity in rmresponse.BusinessEntityCollection.BusinessEntities)
                    {
                        T current = new T();
                        current.Initialize(sdk, entity);

                        results.Add(current);
                    }
                }
            }

            return results;
        }

        /// <summary>
        /// Internal Method. Re-routes the call to <see cref="RetrieveMultiple"/>. This method is used by the lazy loaded properties. It automatically handles CrmService and ICrmService.
        /// </summary>
        /// <typeparam name="T">Type of the entity.</typeparam>
        /// <param name="sdk">Instance of the CRM web service.</param>
        /// <param name="isdk">Instance of the CRM web service interface.</param>
        /// <param name="entityName">Schema name of the entity.</param>
        /// <param name="keyField">Schema name of the key field.</param>
        /// <param name="key">Value of the key field.</param>
        /// <returns>A collection of T.</returns>
        public static List<T> RetrieveOneToMany<T>(CrmService sdk, ICrmService isdk, string entityName, string keyField, Guid key) where T : CrmEntity, new()
        {
            if (sdk != null)
            {
                return RetrieveOneToMany<T>(sdk, entityName, keyField, key);
            }
            else if (isdk != null)
            {
                return RetrieveOneToMany<T>(isdk, entityName, keyField, key);
            }
            return null;
        }

        /// <summary>
        /// Internal Method. Re-routes the call to <see cref="RetrieveMultiple"/>.
        /// </summary>
        /// <typeparam name="T">Type of the entity.</typeparam>
        /// <param name="sdk">Instance of the CRM web service.</param>
        /// <param name="entityName">Schema name of the entity.</param>
        /// <param name="keyField">Schema name of the key field.</param>
        /// <param name="key">Value of the key field.</param>
        /// <returns>A collection of T.</returns>
        public static List<T> RetrieveOneToMany<T>(CrmService sdk, string entityName, string keyField, Guid key) where T : CrmEntity, new()
        {
            return RetrieveMultiple<T>(sdk, entityName, keyField, key);
        }

        /// <summary>
        /// Internal Method. Re-routes the call to <see cref="RetrieveMultiple"/>. This method is using ICrmService.
        /// </summary>
        /// <typeparam name="T">Type of the entity.</typeparam>
        /// <param name="sdk">Instance of the CRM web service.</param>
        /// <param name="entityName">Schema name of the entity.</param>
        /// <param name="keyField">Schema name of the key field.</param>
        /// <param name="key">Value of the key field.</param>
        /// <returns>A collection of T.</returns>
        public static List<T> RetrieveOneToMany<T>(ICrmService sdk, string entityName, string keyField, Guid key) where T : CrmEntity, new()
        {
            return RetrieveMultiple<T>(sdk, entityName, keyField, key);
        }
    }
}
